#!/usr/bin/env ruby
require 'rubygems'
require "yaml"
require 'popen4'

module JCron
	class PID
		def self.running(pid)
			begin
				Process.getpgid(pid.to_i)
				#Process.kill(0, Integer(pid))
				#puts "#{pid} is alive!"
				return true
			rescue Errno::EPERM                     # changed uid
				#puts "#{pid} has escaped my control!";
				return false
			rescue Errno::ESRCH
				#puts "#{pid} is deceased.";      # or zombied
				return false
			rescue
				#puts "Odd; I couldn't check the status"
				return false
			end
		end
	end
	class Timer
		attr_reader :start, :end
		def initialize
			@start       = Time.now
		end
		def end
			@end         = Time.now
		end
		def diff
			Time.now - @start
		end
		def total
			@end - @start
		end
	end
	class Log
		def self.filelog(file,msg)
			if file != nil
				require 'logger'
				logger = Logger.new(file)
				logger.datetime_format = "%Y-%m-%d %H:%M:%S"
				logger.debug msg.to_s
			end
		end
		def self.syslog(level,msg)
			if level != nil
				require 'syslog'
				Syslog.open('jcron')
				Syslog.send(level,msg)
				Syslog.close()
			end
		end
	end
	class Options
		attr_accessor :state, :commands, :time, :control, :log
		@@required = ['state','commands','time','control','log']
		def initialize(options)
			@error = false
			@@required.each { |name|
				if options[name].nil?
					@error = true
				end
			}
		end
		def has_error
			@error
		end
	end
	class Main
		attr_reader :error, :config_file, :options
		def pid_token_replacement(commands,pid)
			commands.each{ |command|
				command.gsub!('{:pid}',pid.to_s)
			}
		end
		def initialize(debug=false)

			@debug             = debug

			@status            = nil
			@error             = false

			@config_file       = nil
			@options           = nil

			@max_min_time      = {:max=>false,:min=>false}
			@timer             = nil
		end
		def watch_pid(pid)
			while PID.running(pid)
				if @debug == true
					puts Time.now.to_s+' '+pid.to_s
				end
				check_too_long
				if @max_min_time[:max] == true
					# you could leverage jcron to wrap a command in this case
					a, b, out_max, err_max = run_commands(pid_token_replacement(@options['time']['max']['commands'],pid))
					# should i output these somewhere?
					#puts 'max commands output: '+out_max.read.strip
					#puts 'max commands output: '+err_max.read.strip
				end
				sleep @options['time']['step'].to_i
			end
		end
		def check_too_short
			if @max_min_time[:min] == false and @timer.diff < @options['time']['min']['seconds']
				@max_min_time[:min] = true
			end
		end
		def check_too_long
			if @max_min_time[:max] == false and @timer.diff > @options['time']['max']['seconds']
				@max_min_time[:max] = true
			end
		end
		def run_commands(commands)
			pid, stdin, stdout, stderr = open4::open4 "sh"
			commands.each{ |command|
				stdin.puts command
			}
			stdin.close 
			return pid, stdin, stdout, stderr
		end
		def get_pid_info(pid)
			ignored, status = Process::waitpid2 pid 
		end
		def run
			if @error == true
				return false
			end
			if @options['state']['run'] == false
				return false
			end
			if @options['state']['file'] != nil
				if !File::exists?(@options['state']['file'])
					return false
				end
				if !File.readable?(@options['state']['file'])
					return false
				end
				File.open(@options['state']['file'], "r").each{ |line|
					if line.strip != '1'
						return false
					end
				}
			end

			@timer = Timer.new

			pid, stdin, stdout, stderr = run_commands(@options['commands'])

			watch_pid(pid)

			check_too_short

			ignored, status = get_pid_info(pid)
	
			stdout = stdout.read.strip
			stderr = stderr.read.strip

			if @debug == true
				puts "pid        : #{ pid }" 
				puts "stdout     : #{ stdout }" 
				puts "stderr     : #{ stderr }" 
				puts "status     : #{ status.inspect }" 
				puts "exitstatus : #{ status.exitstatus }" 
				puts "too long   : #{ @max_min_time[:max] }" 
				puts "too short  : #{ @max_min_time[:min] }" 
			end

			if @max_min_time[:min] == true
				# one could leverage jcron to wrap a command in this case
				a, b, out_min, err_min = run_commands(pid_token_replacement(@options['time']['min']['commands'],pid))
				# should i output these somewhere?
				#puts 'min commands output: '+out_min.read.strip
				#puts 'min commands output: '+err_min.read.strip
			end

			if stdout.length > 0
				if @options['log']['file']['use'] == true
					Log.filelog(@options['log']['file']['stdout'],stdout)
				end
				if @options['log']['syslog']['use'] == true
					Log.syslog(@options['log']['syslog']['stdout'],stdout)
				end
			end
			if stderr.length > 0
				if @options['log']['file']['use'] == true
					Log.filelog(@options['log']['file']['stderr'],stderr)
				end
				if @options['log']['syslog']['use'] == true
					Log.syslog(@options['log']['syslog']['stderr'],stderr)
				end
			end

			@status = status.exitstatus

			@timer.end

			return true
		end
		def set_options(options)
			options_obj = Options.new(options)
			if options_obj.has_error
				@error = true
			else
				@options = options
			end
		end
		def read_args(args)
			if args.nil? or args.count != 1
				@error = true
			else
				@args = args
				@config_file = @args[0].to_s
				options = YAML.load_file(@config_file)
				set_options(options)
			end
		end
		def set_debug(debug)
			@debug = debug
		end
	end
end

cron = JCron::Main.new
cron.read_args(ARGV)
cron.set_debug(false)
result = cron.run
if result == true
	puts 1
else
	puts 0
end
